Перейти к основному содержимому

5.07. Функции

Разработчику Архитектору

Функции

Функция в PHP — это именованный блок программного кода, предназначенный для выполнения конкретной задачи, который можно вызывать многократно в разных контекстах без дублирования реализации. Функции являются базовым строительным элементом процедурного и функционального стиля программирования в PHP и активно используются также в объектно-ориентированном коде — как методы классов и как статические утилиты.

С версии 5.3 язык получил поддержку замыканий и анонимных функций, а с PHP 7.0 — строгую типизацию параметров и возвращаемых значений, что существенно приблизило семантику функций к современным ожиданиям от языка общего назначения.

Объявление именованной функции

Синтаксис объявления именованной функции в PHP начинается с ключевого слова function, за которым следует уникальное в пределах области видимости имя функции, список формальных параметров в круглых скобках, и тело функции в фигурных скобках:

function имя_функции(список_параметров): тип_возврата {
// тело функции
return значение;
}

Имя функции в PHP должно соответствовать правилам идентификаторов: начинаться с буквы или символа подчёркивания, далее — любая комбинация букв, цифр и подчёркиваний. Регистр символов в имени функции не учитывается: foo(), FOO(), Foo() — одно и то же объявление с точки зрения движка. Однако с точки зрения стиля кода и читаемости принято придерживаться camelCase или snake_case (в экосистеме PHP исторически чаще встречается snake_case, особенно в built-in функциях).

Формальные параметры — это переменные, объявленные в сигнатуре функции. При вызове функции им присваиваются фактические аргументы. PHP поддерживает:

  • Позиционные параметры — передаются строго по порядку;
  • Именованные аргументы — доступны начиная с PHP 8.0, позволяют указывать аргументы по имени, игнорируя порядок;
  • Параметры по умолчанию — позволяют опускать аргумент при вызове;
  • Типизация параметров — начиная с PHP 7.0, возможна явная декларация типов для скалярных значений (int, float, string, bool), а также для составных типов (array, callable, iterable, классов и интерфейсов).

Пример с типизацией и явным типом возвращаемого значения:

function greet(string $name): string {
return "Привет, $name!";
}

В этом примере:

  • string $name означает, что функция принимает ровно один аргумент, который должен быть строкой. При передаче значения другого типа (например, целого числа 42) PHP в режиме strict types выбросит TypeError. В режиме по умолчанию (coercive mode) PHP попытается выполнить неявное приведение: 42 станет "42" — и вызов завершится успешно.
  • : string после скобок объявляет, что функция гарантирует возврат значения типа string. Если функция завершается без return, или возвращает null, или возвращает значение другого типа (например, int), — в strict mode будет выброшено исключение; в coercive mode будет предпринята попытка приведения, но для null и intstring это сработает не всегда корректно, и в случае несовместимости — тоже будет TypeError.

Строгий режим типизации включается директивой declare(strict_types=1);, которая должна быть первой инструкцией в файле (после открывающего тега <?php, если он есть). Без этой директивы действует коэрцитивный режим, где возможно автоматическое приведение примитивных типов в ограниченных сценариях (например, intfloat, string содержащая число → int при передаче в int-параметр и т.п.).

Область видимости переменных и доступ к глобальному контексту

Функция в PHP создаёт собственную локальную область видимости. Переменные, объявленные вне функции, не доступны внутри неё, за исключением случаев, когда явно используется ключевое слово global или суперглобальные массивы ($_GET, $_POST, $_SERVER, $_ENV и др.). Использование global считается плохой практикой, так как нарушает принципы инкапсуляции и затрудняет тестирование и сопровождение.

Рекомендуемый подход — передавать все необходимые данные через параметры и возвращать результат явно. Это делает функцию чистой (в смысле функционального программирования: не имеет побочных эффектов, не зависит от внешнего состояния) или, по крайней мере, предсказуемой.

Пример нежелательного использования global:

$prefix = "Уважаемый";

function greet(string $name): string {
global $prefix;
return "$prefix $name";
}

Альтернатива — инъекция зависимостей:

function greet(string $prefix, string $name): string {
return "$prefix $name";
}

echo greet("Уважаемый", "Иван");

Возврат значения и множественные выходы

Функция завершает своё выполнение и возвращает управление вызывающему коду при достижении инструкции return или при конце тела функции. Если return не указан — функция возвращает null. Даже если функция ничего не должна возвращать, явное указание return; (без значения) предпочтительнее неявного завершения: это улучшает читаемость и сигнализирует, что выход из функции — осознанное действие.

PHP не поддерживает возврат нескольких значений напрямую. Однако это легко эмулируется через:

  • возврат массива: return [$x, $y, $status];
  • использование передачи по ссылке (через & в параметре), что позволяет функции модифицировать переменную в вызывающем контексте;
  • применение объектов-обёрток или DTO (Data Transfer Objects), когда логически связанные данные группируются в структуру.

Передача по ссылке — мощный, но рискованный инструмент. Её явное указание в сигнатуре (function modify(&$value)) делает намерение очевидным, но скрывает семантику изменения из вызова: modify($x) выглядит как обычный вызов, хотя $x может быть изменена. Такой стиль требует особой осторожности и чёткой документации.

Анонимные функции и замыкания

Начиная с PHP 5.3, язык поддерживает анонимные функции — функции без имени, которые могут быть присвоены переменным, переданы как аргументы в другие функции или возвращены из функций. Они объявляются с помощью конструкции function(...) { ... }, без имени после function, и часто используются для реализации callback-логики (например, в array_map, usort, array_filter).

Пример:

$greet = function($name) {
return "Здравствуй, $name";
};
echo $greet("Ольга");

Тип переменной $greetClosure. Это встроенный класс PHP, инкапсулирующий исполняемый код анонимной функции. Экземпляры Closure являются callable: их можно вызывать как обычные функции — с помощью синтаксиса $closure(...). Кроме того, они реализуют магический метод __invoke(), что позволяет использовать их в любом контексте, требующем вызываемого объекта.

Важнейшее свойство анонимных функций — поддержка замыканий. Замыкание — это анонимная функция, которая «захватывает» переменные из окружающей лексической области видимости даже после того, как эта область перестаёт существовать. В PHP захват переменных осуществляется явно, с помощью ключевого слова use:

$greeting = "Добро пожаловать";

$greeter = function(string $name) use ($greeting): string {
return "$greeting, $name!";
};

echo $greeter("Мария"); // Добро пожаловать, Мария!

Захват может быть по значению (по умолчанию) или по ссылке — с помощью амперсанда:

$count = 0;

$increment = function() use (&$count) {
$count++;
};

$increment();
$increment();
echo $count; // 2

Обратите внимание: захват по ссылке — единственный способ изменить переменную из внешней области внутри анонимной функции. При захвате по значению копируется текущее значение переменной на момент объявления замыкания, и любые последующие изменения внешней переменной не повлияют на внутреннюю копию.

Анонимные функции и замыкания — основа для функциональных паттернов в PHP: каррирования, частичного применения, создания фабрик, реализации стратегий и обработчиков событий. Их широкое применение наблюдается в фреймворках (Laravel, Symfony), маршрутизаторах, и в асинхронных библиотеках (например, ReactPHP).

Различие между function и fn()

Начиная с PHP 7.4 появилась краткая форма объявления анонимных функций — arrow functions (стрелочные функции), синтаксис fn(...) => выражение. Они всегда однострочные, всегда возвращают результат вычисления выражения (неявный return), и автоматически захватывают переменные из родительской области по значению — без необходимости use.

Пример:

$factor = 3;
$multiply = fn($x) => $x * $factor;

echo $multiply(5); // 15

Это эквивалентно:

$factor = 3;
$multiply = function($x) use ($factor) {
return $x * $factor;
};

Захват по значению исключает случайную модификацию внешнего состояния. Однако стрелочные функции не могут содержать блочный код (циклы, if без тернарного оператора, try/catch), не поддерживают return явно, и не позволяют захват по ссылке.

Встроенные и пользовательские функции

PHP поставляется с обширной стандартной библиотекой, насчитывающей более 7000 встроенных функций, охватывающих работу с текстом, массивами, датами, файлами, сетью, базами данных, криптографией и др. Эти функции объявлены в глобальной области видимости и доступны всегда (если не отключены настройками disable_functions в php.ini).

Пользовательские функции, определённые разработчиком, существуют в той же глобальной области, если не используются пространства имён. Рекомендуется использовать пространства имён для изоляции:

namespace Utils;

function sanitize(string $input): string {
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}

Тогда вызов будет: \Utils\sanitize($str) или use function Utils\sanitize; sanitize(...).

Конфликты имён между встроенными и пользовательскими функциями недопустимы: PHP не позволяет переопределить существующую функцию, даже если она объявлена в другом пространстве имён с тем же именем. Повторное объявление функции (function foo() {} function foo() {}) приведёт к фатальной ошибке на этапе компиляции.

Производительность и компиляция

В современных версиях PHP (7.0+) функции компилируются в опкод (bytecode) и кэшируются в OPcache при повторных запросах. Это делает вызов пользовательских функций практически таким же быстрым, как и встроенных — накладные расходы минимальны. Однако следует избегать излишней вложенности вызовов («функциональный ад»), особенно в критичных к производительности участках (циклах), поскольку каждый вызов всё же требует создания стекового фрейма и передачи параметров.

Типизация параметров и возвращаемых значений повышает надёжность кода и даёт OPcache дополнительную информацию для оптимизации: например, при string $s движок может пропустить проверки типа при внутренних операциях со строкой.


Рекурсивные функции и ограничения стека вызовов

Рекурсия в PHP реализуется так же, как и в большинстве императивных языков: функция вызывает саму себя, либо напрямую, либо косвенно через цепочку других функций. PHP не оптимизирует хвостовую рекурсию (tail call optimization), поэтому каждый рекурсивный вызов создаёт новый фрейм в стеке вызовов. Глубина стека ограничена настройкой zend.max_allowed_stack_size (задаётся в байтах) и, в меньшей степени, параметром xdebug.max_nesting_level, если установлен расширение Xdebug.

По умолчанию максимальная глубина вложенности вызовов составляет 256 (в Xdebug) или значительно выше в «чистом» Zend Engine — но точное значение зависит от конфигурации сервера и объёма данных в каждом фрейме. Превышение лимита приводит к фатальной ошибке: Fatal error: Maximum function nesting level of 'N' reached.

Пример простой рекурсивной функции — вычисление факториала:

function factorial(int $n): int {
if ($n <= 1) {
return 1;
}
return $n * factorial($n - 1);
}

Хотя этот код корректен с математической точки зрения, его применение для больших n (например, factorial(10000)) гарантированно вызовет переполнение стека. В таких случаях рекомендуется использовать итеративную реализацию:

function factorial(int $n): int {
$result = 1;
for ($i = 2; $i <= $n; $i++) {
$result *= $i;
}
return $result;
}

Рекурсия оправдана при работе с древовидными или вложенными структурами данных: обход файловой системы (RecursiveDirectoryIterator — это итератор, но его можно эмулировать через рекурсивную функцию), разбор вложенных массивов, JSON-объектов, XML-документов, AST (абстрактных синтаксических деревьев). В таких сценариях глубина рекурсии, как правило, ограничена природой данных (например, уровень вложенности директорий редко превышает 20–30), и риск переполнения минимален.

Важно: при проектировании рекурсивной функции всегда выделяйте базовый случай (условие завершения), иначе рекурсия станет бесконечной. PHP не обнаруживает бесконечную рекурсию заранее — она приведёт к зависанию процесса или исчерпанию памяти/стека.


Вариадические функции и оператор распаковки ...

Вариадическая функция — это функция, принимающая переменное число аргументов. В PHP поддержка таких функций реализована через оператор ... (так называемый splat operator или spread operator), введённый в PHP 5.6.

Есть два основных сценария использования:

  1. Сбор аргументов в массив — при объявлении функции:

    function sum(int ...$numbers): int {
    $total = 0;
    foreach ($numbers as $n) {
    $total += $n;
    }
    return $total;
    }

    echo sum(1, 2, 3, 4); // 10
    echo sum(); // 0

    Здесь ...$numbers означает: «собрать все переданные аргументы в массив $numbers». Типизация int ...$numbers гарантирует, что каждый аргумент будет приведён или проверен на соответствие типу int. Передача нецелого значения вызовет ошибку в strict mode или приведение — в coercive.

  2. Распаковка массива в аргументы — при вызове функции:

    $values = [10, 20, 30];
    echo sum(...$values); // эквивалентно sum(10, 20, 30)

    Оператор ... перед массивом «раскрывает» его элементы как отдельные позиционные аргументы. Это особенно полезно при делегировании вызовов (например, в прокси-функциях, декораторах, или при переопределении методов в наследниках классов).

Вариадические параметры могут комбинироваться с обычными, но должны идти последними в сигнатуре:

function log(string $level, string $message, mixed ...$context) {
// $level и $message — обязательные
// $context — опциональные дополнительные данные
}

Семантически, вариадические функции — это обобщение функций с параметрами по умолчанию, когда число параметров заранее неизвестно. Они позволяют строить гибкие интерфейсы без необходимости передавать массив вручную (log('error', '...', ['user_id' => 123])), сохраняя при этом читаемость вызова.


Обработка ошибок и семантика возврата

PHP исторически использует неоднородные подходы к сигнализированию об ошибках:

  • Возврат false — в функциях, возвращающих скаляр (strpos, file_get_contents, json_decode при нестрогом режиме и т.д.);
  • Возврат null — при отсутствии результата (например, array_search, если элемент не найден);
  • Генерация предупреждений/уведомленийE_WARNING, E_NOTICE через trigger_error() или внутренние механизмы;
  • Выброс исключений — начиная с PHP 5, особенно в объектно-ориентированном коде и в новых расширениях (например, DateTime, PDO, JsonException с PHP 7.3 при JSON_THROW_ON_ERROR).

Это приводит к тому, что вызывающий код обязан знать, как конкретная функция отражает ошибочное состояние — и проверять результат соответствующим образом.

Например, strpos() возвращает 0, если подстрока найдена в начале строки, и false, если не найдена. Поэтому неправильно писать:

if (strpos($haystack, $needle)) { /* ... */ } // ошибка: 0 интерпретируется как false

Правильно — использовать строгое сравнение:

if (false !== strpos($haystack, $needle)) { /* найдено */ }

Современные практики рекомендуют:

  • Использовать исключения вместо false/null в пользовательском коде, особенно в библиотеках и слоях ядра приложения. Исключения явно сигнализируют об исключительной ситуации, не смешиваются с валидными данными и обеспечивают стек вызовов для отладки.
  • Применять тип ?T (nullable) с явной проверкой на null, если отсутствие результата — ожидаемый сценарий (например, getUserById(int $id): ?User).
  • Избегать смешивания false и значимых данных в одном типе возврата. Если функция может вернуть строку или провалиться — лучше выбросить исключение, либо вернуть объект-результат (например, Result<string, Error> через пользовательский класс или библиотеку вроде spatie/result).

С PHP 8.1 появилась поддержка never-типа (: never), используемого для функций, которые никогда не возвращают управление (например, всегда выбрасывают исключение или вызывают exit()):

function abort(int $code): never {
http_response_code($code);
exit;
}

Это помогает анализаторам кода (PHPStan, Psalm) точнее отслеживать поток управления.


Тип callable и интерфейс вызываемости

В PHP существует понятие callable — сущности, которую можно вызвать с помощью синтаксиса (...) или call_user_func(). К callable относятся:

  • Имена функций как строки: 'strlen', 'greet';
  • Анонимные функции и экземпляры Closure;
  • Массивы вида [$object, 'method'] — для вызова метода экземпляра;
  • Массивы вида ['ClassName', 'method'] — для статических методов;
  • Объекты, реализующие магический метод __invoke().

Проверка на callable осуществляется функцией is_callable(), а проверка типа в параметрах — через псевдотип callable:

function apply(callable $fn, $value) {
return $fn($value);
}

Однако важно понимать: callable — это структурный тип, а не номинальный. Он не гарантирует, что вызов завершится успешно — только то, что синтаксис вызова допустим. Например:

apply(['stdClass', 'nonExistentMethod'], 42); // is_callable() вернёт true, но вызов упадёт с Error

Поскольку call_user_func() и callable используют динамическое связывание, статические анализаторы не могут всегда предсказать корректность вызова. Поэтому в критичных участках рекомендуется:

  • Использовать строгую типизацию параметров методов (например, Closure вместо callable, если допустимы только замыкания);
  • Применять интерфейсы с единственным методом (например, interface Validator { public function validate(mixed $value): bool; }) — это даёт compile-time проверку и лучше отражает намерение;
  • Избегать передачи строк-имён функций в публичные API — это снижает рефакторингопригодность и усложняет поиск зависимостей.

Псевдофункции: языковые конструкции, имитирующие функции

Некоторые часто используемые «функции» в PHP на самом деле не являются функциями, а представляют собой языковые конструкции. К ним относятся:

  • echo, print — вывод данных;
  • include, require, include_once, require_once — загрузка файлов;
  • isset(), empty(), unset() — работа с существованием и содержимым переменных;
  • die(), exit() — принудительное завершение скрипта.

Главное отличие: языковые конструкции не требуют скобок (хотя для isset, empty, unset скобки обязательны синтаксически), не могут быть сохранены в переменную, не могут быть переданы как callable, и не подвержены обычным правилам области видимости.

Например, isset($var) может принимать недекларированные переменные без предупреждения — потому что проверка существования происходит до вычисления аргумента. Если бы isset была настоящей функцией, вызов isset($undefined) привёл бы к Notice: Undefined variable, так как аргументы функции вычисляются до передачи.

Аналогично, include 'file.php' интерпретируется как часть синтаксиса, а не как вызов — поэтому он может возвращать значение из подключаемого файла (return 42; внутри file.php), и это значение будет результатом выражения include.

Понимание этого различия критично при рефакторинге и при написании гибкого кода: нельзя, например, сделать $logger = echo; $logger("test"); — это синтаксическая ошибка. Вместо этого следует использовать обёртки: function output(string $s) { echo $s; }.


Лучшие практики проектирования функций в PHP

Завершая теоретическую часть, систематизируем рекомендации по написанию функций, основанные на принципах читаемости, тестируемости и сопровождаемости:

  • Одна функция — одна обязанность. Функция должна решать одну конкретную задачу на одном уровне абстракции. Если в названии появляются союзы «и», «или», «затем» — это сигнал к декомпозиции.
  • Короткие сигнатуры. Оптимально — до 3–4 параметров. При большем числе возникает когнитивная нагрузка, растёт риск ошибок при передаче. Альтернатива — передача объекта конфигурации (DTO) или применение шаблона Builder.
  • Явные имена. Название должно отражать действие и эффект: calculateTotalPrice(), validateEmailAddress(), fetchUserById(). Избегайте общих слов: process(), handle(), doSomething().
  • Избегайте побочных эффектов. Функция не должна изменять глобальное состояние, писать в файлы, отправлять HTTP-запросы или модифицировать переданные по значению аргументы — если это не является её прямой обязанностью. Побочные эффекты должны быть явными и локализованными.
  • Строгая типизация. Используйте declare(strict_types=1) в каждом файле. Декларируйте типы параметров и возвращаемых значений. Это предотвращает неявные ошибки приведения и улучшает работу IDE и статических анализаторов.
  • Обработка ошибок через исключения. Если функция не может выполнить свою задачу — она должна сообщить об этом не через false или null, а через выброс исключения, наследуемого от \Exception или \Throwable.
  • Документирование через PHPDoc и/или атрибуты. Даже при наличии типов, пояснения к поведению, ограничениям, исключениям и неочевидным граничным случаям необходимы. Например:
    /**
    * Парсит строку в формате ISO 8601 и возвращает объект DateTimeImmutable.
    *
    * @param string $isoString Должна содержать дату и время с временной зоной, например '2025-11-18T15:30:00+03:00'
    * @throws \InvalidArgumentException если строка не соответствует формату
    * @return \DateTimeImmutable
    */
    function parseIsoDateTime(string $isoString): \DateTimeImmutable { /* ... */ }